package com.jindi.jacococli.biz.business.service;

import com.jindi.jacococli.biz.business.model.ExcelDataModel;
import com.jindi.jacococli.biz.config.AppProperties;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.jacoco.core.analysis.Analyzer;
import org.jacoco.core.analysis.CoverageBuilder;
import org.jacoco.core.analysis.IBundleCoverage;
import org.jacoco.core.analysis.ICounter;
import org.jacoco.core.analysis.IPackageCoverage;
import org.jacoco.core.tools.ExecFileLoader;
import org.jacoco.report.DirectorySourceFileLocator;
import org.jacoco.report.FileMultiReportOutput;
import org.jacoco.report.IReportVisitor;
import org.jacoco.report.html.HTMLFormatter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import static com.jindi.jacococli.biz.constant.Constant.*;

/**
 * jacoco report 生成 service
 */
@Service
public class JacocoReportService {

    private static final Logger LOGGER = LoggerFactory.getLogger(JacocoReportService.class);

    @Resource
    private AppProperties appProperties;

    public boolean createReport(String projectDir, String execPath, String service, String reportPath, ExcelDataModel data) {
        List<String> modules = projectModule(projectDir);
        if (CollectionUtils.isEmpty(modules)) {
            return false;
        }
        ExecFileLoader execFileLoader = loadExecutionData(execPath);
        if (Objects.isNull(execFileLoader)) {
            return false;
        }
        IBundleCoverage bundleCoverage = analyzeStructure(execFileLoader, modules, service);
        if (Objects.isNull(bundleCoverage)) {
            return false;
        }
        // 获取总的解析类数据
        populateData(bundleCoverage, data);
        return doReport(execFileLoader, bundleCoverage, modules, reportPath);
    }

    public List<String> projectModule(String basePath) {
        List<String> moduleFileName = new ArrayList<>();
        File directory = new File(basePath);
        if (directory.exists() && directory.isDirectory()) {
            File[] files = directory.listFiles();
            if (Objects.nonNull(files)) {
                for (File file : files) {
                    String name = file.getName();
                    if (file.isDirectory() && (name.endsWith(API_SUFFIX) || name.endsWith(PROVIDER_SUFFIX) || name.endsWith(BIZ_SUFFIX)
                        || name.endsWith(SDK_SUFFIX))) {
                        moduleFileName.add(basePath + BACKSLASH + file.getName());
                    }
                }
            }
        } else {
            LOGGER.info("git dictionary is not exist!");
        }
        return moduleFileName;
    }

    private IBundleCoverage analyzeStructure(ExecFileLoader execFileLoader, List<String> modules, String service) {
        try {
            final CoverageBuilder coverageBuilder = new CoverageBuilder();
            final Analyzer analyzer = new Analyzer(execFileLoader.getExecutionDataStore(), coverageBuilder);

            for (String module : modules) {
                analyzer.analyzeAll(new File(module + TARGET_CLASS));
            }
            return coverageBuilder.getBundle(service);
        } catch (IOException e) {
            LOGGER.error("analyzer.analyzeAll error", e);
        }
        return null;
    }

    private ExecFileLoader loadExecutionData(String execPath) {
        File execFile = new File(execPath);
        if (!execFile.exists()) {
            LOGGER.info("jacoco execFile is not exist!");
            return null;
        }
        try {
            ExecFileLoader execFileLoader = new ExecFileLoader();
            execFileLoader.load(execFile);
            return execFileLoader;
        } catch (IOException e) {
            LOGGER.error("execFileLoader.load error!", e);
        }
        return null;
    }

    private boolean doReport(final ExecFileLoader execFileLoader, final IBundleCoverage bundleCoverage, List<String> modules,
                             String reportDir) {
        final HTMLFormatter htmlFormatter = new HTMLFormatter();
        try {
            IReportVisitor visitor = htmlFormatter.createVisitor(new FileMultiReportOutput(new File(reportDir)));
            visitor.visitInfo(execFileLoader.getSessionInfoStore().getInfos(), execFileLoader.getExecutionDataStore().getContents());
            // Populate the report structure with the bundle coverage information.
            // Call visitGroup if you need groups in your report.
            for (String module : modules) {
                String directory = module + SRC_PATH;
                visitor.visitBundle(bundleCoverage, new DirectorySourceFileLocator(new File(directory), StandardCharsets.UTF_8.name(), INT4));
            }
            visitor.visitEnd();
            return true;
        } catch (IOException e) {
            LOGGER.error("createReport error!", e);
        }
        return false;
    }

    private void populateData(IBundleCoverage bundleCoverage, ExcelDataModel data) {
        Set<String> excludePackages = appProperties.getExcludePackages();
        Collection<IPackageCoverage> packages = bundleCoverage.getPackages();
        if (CollectionUtils.isEmpty(packages)) {
            return;
        }
        excludePackages = Objects.isNull(excludePackages) ? new HashSet<>() : excludePackages;
        excludePackages = excludePackages.stream().filter(StringUtils::isNoneBlank)
                .map(exclude -> exclude.replace(".", "/")).collect(Collectors.toSet());
        long classes = 0;
        long missed = 0;
        long excludedClasses = 0;
        long excludedMissed = 0;
        for (IPackageCoverage coveragePackage : packages) {
            ICounter classCounter = coveragePackage.getClassCounter();
            if (Objects.isNull(classCounter)) {
                continue;
            }
            int totalCount = (int) coveragePackage.getClassCounter().getValue(ICounter.CounterValue.TOTALCOUNT);
            int missedCount = (int) coveragePackage.getClassCounter().getValue(ICounter.CounterValue.MISSEDCOUNT);
            classes = classes + totalCount;
            missed = missed + missedCount;
            boolean suffixPackage = false;
            for (String excludePackage : excludePackages) {
                if (coveragePackage.getName().endsWith(excludePackage)) {
                    suffixPackage = true;
                    break;
                }
            }
            if (!suffixPackage) {
                excludedClasses = excludedClasses + totalCount;
                excludedMissed = excludedMissed + missedCount;
            }
        }
        data.setClasses(classes);
        data.setMissed(missed);
        data.setExcludeClasses(excludedClasses);
        data.setExcludeMissed(excludedMissed);
    }

}
